package gm;

object gm extends TypeFuns {

trait GenericComp requires (CoreConvsComp with GenericComp) {
  trait Generic[g <: TypeFun1] {
    def unit: app1[g, Unit]
    def plus[aa, b](implicit c1: aa=>Rep[aa], c2: b=>Rep[b]): app1[g, Plus[aa, b]]
    def pair[aa, b](implicit c1: aa=>Rep[aa], c2: b=>Rep[b]): app1[g, Pair[aa, b]]
    def datatype[aa, b](iso: Iso[aa,b])(implicit c1: aa=>Rep[aa]): app1[g, b]
    def char: app1[g, Char]
    def int: app1[g, Int]
  
    implicit val self = this
  }

  trait Rep[t] {           
    def rep[g <: TypeFun1](implicit gen: Generic[g]): app1[g, t]
  }
  
  trait Plus[a,b]
    case class Inl[a,b](el: a) extends Plus[a,b]
    case class Inr[a,b](el: b) extends Plus[a,b]

  case class Iso[t,dt](fromData: dt=>t, toData: t=>dt)
}

trait CoreConvsComp requires (CoreConvsComp with GenericComp) {
  type XConvs <: CoreConvs;  
  val convs: XConvs

  trait Convs {
    // TODO: requires type abstraction at argument scope
//    def RepI[t]([g <: TypeFun1] f: Generic[g]=>app1[g, t]) = new Rep[t]{def rep[g <: TypeFun1](implicit gen: Generic[g]): app1[g, t]=f(gen)}    
  }

  trait CoreConvs requires XConvs extends Convs { 
    implicit def unit2rep(u: Unit): Rep[Unit] = new Rep[Unit]{def rep[g <: TypeFun1](implicit gen: Generic[g]): app1[g, Unit]= gen.unit }//  RepI (.unit)
    implicit def int2rep(i: Int): Rep[Int] = new Rep[Int]{def rep[g <: TypeFun1](implicit gen: Generic[g]): app1[g, Int]= gen.int } //RepI (.int)
    implicit def char2rep(c: Char): Rep[Char] = new Rep[Char]{def rep[g <: TypeFun1](implicit gen: Generic[g]): app1[g, Char]= gen.char } //RepI (.char)
    implicit def pair2rep[t, u](p: Pair[t, u])(implicit c1: t=>Rep[t], c2: u=>Rep[u]): Rep[Pair[t, u]] 
        = new Rep[Pair[t, u]]{def rep[g <: TypeFun1](implicit gen: Generic[g]): app1[g, Pair[t, u]]= gen.pair } //RepI (.pair(c1, c2)) // TODO: why do c1, c2 have to be passed explicitly? -- if I inline mr, it's not necessary
    implicit def plus2rep[t, u](p: Plus[t, u])(implicit c1: t=>Rep[t], c2: u=>Rep[u]): Rep[Plus[t, u]]  
        = new Rep[Plus[t, u]]{def rep[g <: TypeFun1](implicit gen: Generic[g]): app1[g, Plus[t, u]]= gen.plus } //RepI (.plus(c1, c2))
  }
}


trait ShowBinComp requires (CoreConvsComp with GenericComp with ShowBinComp) {
  class ShowBinTC extends Generic[ShowBin] {
    def unit = ShowBin ((x: Unit) => Nil) 
    def plus[a, b](implicit c1: a=>Rep[a], c2: b=>Rep[b]) = ShowBin (
        (x: Plus[a, b]) => x match {
          case Inl(el) => _0 :: showBin(el)
          case Inr(el) => _1 :: showBin(el)
        }) 
    
    def pair[a, b](implicit c1: a=>Rep[a], c2: b=>Rep[b]) = ShowBin (
        (x: Pair[a, b]) => showBin(x._1) ::: showBin(x._2))
    
    def datatype[a, b](iso: Iso[a,b])(implicit c1: a=>Rep[a]) = ShowBin (
        (x: b) => showBin(iso.fromData(x)))
    
    def char = ShowBin ((x: Char) => bits(7,x))
    def int = ShowBin ((x: Int) => bits(16,x))

    def showBin[t](x: t)(implicit c: t=>Rep[t]): Bin = x.rep[ShowBin].apply(x) 
  }
  
  def ShowBin[aa](f: aa => Bin) = new ShowBin{type a=aa; val apply = f} 
  trait ShowBin extends TypeFun1{ val apply: a => Bin }

  type Bin = List[Bit]
  trait Bit
    case object _0 extends Bit
    case object _1 extends Bit
  def bits(n: Int, x: Int): Bin = if(n==0) Nil else {if((x&1)==1) _1 else _0} :: bits(n-1,x>>1)
}


trait ListConvComp requires (CoreConvsComp with ListConvComp with GenericComp) {
  type XConvs <: (CoreConvs with ListConv)
  
  trait ListConv requires XConvs extends Convs {
    def fromList[t](l: List[t]): Plus[Unit, Pair[t, List[t]]] = l match {
      case Nil => Inl(())
      case x :: xs => Inr(Pair(x, xs))
    }
    
    def toList[t](l: Plus[Unit, Pair[t, List[t]]]): List[t] = l match {
      case Inl(())=> Nil 
      case Inr(Pair(x, xs)) => x :: xs 
    }
    
    implicit def list2rep[t](l: List[t])(implicit c: t=>Rep[t]): Rep[List[t]] 
      = new Rep[List[t]]{def rep[g <: TypeFun1](implicit gen: Generic[g]): app1[g, List[t]]= gen.datatype(Iso(fromList[t], toList[t])) } //RepI (.datatype(Iso(fromList[t], toList[t])))  // TODO: have to supply [t], why?
  }     
}

object deploy extends GenericComp with ShowBinComp with CoreConvsComp with ListConvComp {
  type XConvs = CoreConvs with ListConv
  val convs = new CoreConvs with ListConv
}
}



// testpad:
// {val sb = new ShowBin(); import sb.convs._; sb.showBin(3)}
// {val sb = new ShowBin(); import sb.convs._; sb.showBin(List(3,5))}
//{val sb = new ShowBin(); import sb.convs._; sb.showBin("Lisa".toList)}
//{val sb = new ShowBin(); import sb.convs._; sb.showBin("Lisa")} // --> string2seq is not chained with list2rep